查看原文
其他

分布式场景下基于重试机制的一致性解决方案

马文斌 58技术 2022-03-15


导语

本文针对业务中异常场景处理问题,介绍了基于分布式调度机制的通用重试方案,该方案保证了业务的最终一致性,且大大降低了人工恢复异常数据的成本。。


背景

在分布式环境下,我们大部分的系统都不是单独存在的,系统与系统之间的相互依赖已是常态。
从宏观角度看,分布式“依赖”即增加了系统的整体复杂度,也增加了系统出现问题的概率。例如:在做下单操作时,调用支付系统成功,但是最终添加商品超时;当业务系统依赖的下游服务出现严重的系统问题时,导致在一段时间内的业务受到影响。系统问题最终都会体现到数据一致性上。而在解决一致性问题上,前辈们已经总结出通用的解决方案:分布式式一致性算法(paxos、两阶段提交、三阶段提交等)、最终一致性(重试、补偿、TCC)、对账。
采用分布式一致性算法去解决分布式一致性问题虽然是比较彻底的解决方案,但是实现太过复杂,并且根据CAP理论,这种方式会降低系统整体可用性。因此在我们的业务系统中,大部分都依赖最终一致性方案去解决分布式一致性问题。
“重试”是比较常用的最终一致性方案:
  • “三次重试”:是最直接的方式,能减少偶发性的网络故障带来的异常。但是对于极端情况,三次重试不仅无效,而且还会对下游系统造成更大压力。
  • 通过重试队列实现梯度重试:使用这种方式会根据重试次数的增加去延长下次重试时间(随着重试次数增加,但是重试依然不成功,说明目标系统恢复时间比较长,因此可以根据重试次数延长下次重试时间)。这种方式即能有效提高重试成功的几率,也能通过柔性化的重试避免对下游系统造成更大压力。但是实现起来比较复杂,成本比较高。
  • 人工修复:人工修复也是重试,只不过需要人工操作。当走到这一步时,说明已经造成了线上事故,我们通常的处理方式就是:筛选日志->开发修复脚本->修复脚本测试->批量修复。这个流程已经相当于一个小型的需求开发,我们需要花费至少1天时间去修复。如果业务逻辑复杂,系统日志不全,需要花费更多的时间去修复。
相信做过后端开发的同学对于上述方案的痛点感同身受。因此我们开发了基于分布式调度的分布式重试系统。系统提供了多样化的重试策略;能自动探测目标系统的恢复情况,并进行数据的自动恢复;提供了可视化的操作平台,降低了人工修复数据的成本;并提供了基于异常点的监控报警机制,提高问题定位的效率。


整体介绍





整体架构

1、概念介绍:

集群:即接入方系统的集群标识。
重试点:重试执行的入口,重试点可根据业务自定义重试策略(重试次数、重试间隔、重试数据有效期等)。
节点:代表集群中的某个机器IP。

2、模块介绍:
  A、重试客户端:通过jar包形式提供给业务方统一接入:
  • 上报数据:提供了统一的数据上报方式,利用公司的MQ组件进行数据的上报。
  • 重试点执行:基于jetty server的形式封装了RPC服务,接收重试中心的调用请求,通过重试点作为入口进行重试逻辑的执行。
  • 心跳保持:心跳保持模块会每隔10s向注册管理模块发起节点注册。
  B、注册管理:负责集群、重试点、客户端节点和报警配置的注册管理。
  C、重试中心:负责重试策略的统一控制和调度:
  • 数据收集:负责接收重试客户端的数据上报。
  • 事件监听:负责监听重试事件,触发重试执行,比如手动重试触发、业务方根据系统恢复情况进行重试触发。
  • 自动重试:根据重试点设置的重试策略进行规律型的重试执行,比如:固定间隔时间重试、梯度间隔重试、用户自定义间隔重试。
  • 限流熔断:限流会根据集群和重试点设置的QPS阈值进行重试调度的速率,避免在极端情况对业务系统造成压力;熔断是根据重试调用客户端反馈的结果进行统计,如果反馈结果的失败率达到设定的阈值,则说明业务系统段时间不可恢复,则进行重试中断,并将重试点交给探测恢复模块进行自动恢复。
  D、监控报警:根据报警配置,对重试队列进行监控报警,报警信息包括:集群、重试点(可对应到异常发生的地方)、队列数据量。


详细介绍

1、重试点

对于重试,就是从什么地方发生异常,就从什么地方开始重试,因此“重试点”其实就可以认为是异常发生的地方。假设有如下的业务流程:




重试点拆分
他如果在B、C容易出现异常,则可以抽象出两个重试点:重试点1执行B->C->D,重试点2执行C->D。

2、从异常发生到重试执行





重试客户端与重试中心交互图 

  • 当异常发生时,在异常点处上报重试数据;

  • 重试中心将接收到的数据写入到重试队列中;

  • 自动重试扫描模块会扫描出重试队列需要重试的数据进行重试执行,如果执行成功,则数据出队,如果重试失败,则更新下次重试时间;

  • 在自动重试时,会调用熔断器,如果熔断器反馈是false,则将重试点加入探测队列,标记重试点为探测状态;

  • 熔断器会异步统计最近一段时间内的执行日志,如果失败率达到一定的阈值,则说明目标系统短时间内不可恢复,所以中断当前的自动重试,并将该重试点加入探测队列;

  • 探测恢复模块会自动去使用少量数据去进行重试的探测,如果反馈结果全部成功,则将该探测数据从探测队列出队,并标记重试点状态为自动重试状态;

  • 重试执行通过异步分发执行,并会调用限流器进行重试点维度的限流控制;在对单条数据进行调度时,会通过路由器通过轮询方式筛选出调用的目标节点,当调度时,发现目标节点失效,则会轮询选择下个节点进行调用;

  • 重试客户端的jetty server收到调用请求时,会选择一个worker执行请求;worker会通过调度的信息找到目标的重试点进行重试业务逻辑的执行:执行成功,返回SUCCESS,失败返回返回FAIL。

3、重试数据的可靠性重试策略

  • 重试执行时,避免当前重试点的数据上报:





校验是否从重试点进行调用 
  • 使用公司自研MQ组件wmb进行重试数据上报,send and receive ok 提高发送的可靠性,重试中心消费消息进行pull and ack机制进行消费,保证消费的可靠性;在重试数据上报时,会带一个uuid,作为重试数据入队的幂等性校验的依据。


4、重试策略
  4.1、支持多样化的重试策略
常见的重试方式有固定间隔事件重试、梯度重试、或者由人工等外部因素发起重试。因此重试的触发包括两种:事件触发和自动触发。
  • 事件触发可以由用户手动操作或业务系统自定义发起重试操作;

  • 自动触发是由系统自动发起,主要分为三个阶段:重试点读取、重试数据扫描和重试调度:




自动触发基本流程
  A、重试点读取是分页读取,会读取状态(retry point state)为正常状态的重试点数据;

  B、扫描前,会尝试获取该重试点粒度的分布式锁,如果获取成功,则进行步骤c扫描重试数据;如果获取失败,说明该重试点在其他节点正在执行,则继续遍历下一个重试点;
  C、重试数据扫描阶段会扫描重试数据状态(retry data state)为未执行状态并且下次执行时间(nexttime)小于等于当前时间的数据,只取前5000条数据,避免单节点执行的压力;扫描出的数据会标识为执行中状态,并异步进入重试执行阶段;遍历完成后,释放锁;
  D、异步执行阶段,主要是记录执行日志,发起RPC调用,并根据执行结果进行数据状态变更:如果重试成功,则当前数据出队;如果重试失败,则校验数据重试次数是否达到上限,或数据是否过期,如果是,则淘汰数据到触发重试队列,否则更新当前数据的下次重试时间(nexttime)、重试次数(retry count)加1和状态(retry data state)标志为未执行;

  • 自动重试时,下次重试时间(nexttime)的计算:
    当重试次数n=0时,说明是首次重试,会立即触发重试;
    定义函数interval=f(n),值域∈N+∩(0,expire],定义域n∈N+∩[1,maxNum]

  A、interval代表下次重试时间离当前时间(currentTime)差;
  B、n代表重试次数;
  C、expire代表数据有效期;
  D、maxNum代表最大重试次数;
  E、c代表每次重试的间隔时间c>=1∩c∈N+;
因此可定义下次的重试时间点nextRetryTime= f(n) + currentTime;
  A、常量法:Interval=f(n)=c,代表固定间隔时间的重试策略;
  B、函数法,代表梯度间隔时间的重试策略:
    a)、f(n)=cn;
每次的间隔时间均匀增加;
    b)、Interval=f(n)=c*2^n
每次的间隔时间增加为c的2^n倍;
  C、自定义时间点法:
Interval=f(n)={1,3,5,10,15};
自定义法的重试次数为数组长度length+1次,即n∈[1,length+1]。

  4.2、重试限速
  • 背景:在极端情况下,如果出现大规模的重试,则会对目标系统或目标系统的下游系统造成极大的压力。因此在重试时,需要进行限速。
  • 实现:




重试限速的实现
  • 限流是根据设定的限流阈值进行限制,采用zookeeper的动态节点监听+单机令牌桶算法实现了伪分布式限流,单机QPS=总QPS/节点数。


  4.3、智能化重试
  • 背景:由于目标系统的恢复时间不可预知,通过规律的重试后(固定间隔重试、梯度重试等),依然不能重试成功,这样即造成了大量无用的重试(重试成功率过低),也最终要依赖人工恢复。因此我们实现了相对智能化的重试策略。

  • 分析:由于我们采用的是分布式调度模型,因此在重试中心很容易能监控到每个重试点执行日志。我们根据日志,通过统计分析模型就可以分析出重试目标系统的健康状况,这样我们可以通过统计分析结果,做出相应的熔断和探测恢复,实现智能化重试。

  • 实现:





智能重试实现

  A、熔断:对重试日志进行统计分析,并能分析出重试目标系统的健康状态(失败率达到一定阈值)。熔断后的重试点标记探测状态,并加入探测队列。
  B、探测恢复:根据统计分析模型去分析目标系统的恢复情况:系统会自动的使用少量的数据去尝试重试,如果反馈成功率是100%,则可以判断目标系统已经恢复正常,此时将该重试点出队,并标记该重试点状态为正常状态。


总结

分布式重试系统其实是一个“合并同类项”后的成果,重试的思路都来源于日常的总结,基本适用于大多数的重试场景。分布式重试系统可以是对业务系统的一种自我恢复能力的补充,能提高业务系统的稳定性。当出现系统问题时,如果通过重试系统进行自动恢复,那就不会造成事故。对于需要人工确认的异常数据,提供了可视化的操作界面,降低了处理问题的成本。
分布式重试系统除了提高系统自我恢复能力,也可基于重试点去进行精细化监控报警。重试点即能基本对应到异常发生的地方,因此可以提高问题排查的效率。针对重试数据统计分析,我们也可以对业务系统的健康度做一些评估,为业务系统迭代优化提供依据。
分布式系统并没有做到很完美,“重试点”的切分还是需要依赖人工去做,这样很容易会有所疏漏。如果能通过一种技术方案,系统自动识别到系统“重试点”,并自动生成重试业务逻辑代码,那就更加完美了。大家如果有好的想法欢迎能一起交流沟通,只有思想不断的碰撞,才能产生更完美的方案。


作者简介

马文斌,营销技术团队后端开发工程师。参与过业务系统的重构、营销平台的搭建,负责设计开发了分布式调度平台和分布式重试调度系统等横向技术项目。


END

阅读推荐

房产基于Swoole的PHP RPC框架设计
应用AST技术实现自动化升级React 15至React 16的解决方案
深度文本表征与深度文本聚类在小样本场景中的探索与实践
前端爬虫攻防之接口签名方案
58商家通Android端WebView加载优化方案
并发在58二手车列表的应用







您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存